#!/usr/bin/env bash

# Copyright © 2018 Anton Tsyganenko
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


# ВАЖНО: файлы в бэкапах не предназначены для изменения. Изменяя один
# файл, вы изменяете этот файл во всех бэкапах, где он был таким-же. Это
# же жесткие ссылки. Если хотите изменять -- создавайте копию
# (где-нибудь подальше от бэкапов).

# Скрипт-документация написан Антоном Цыганенко 2018-03-26, с
# использованием идей из
# https://earlruby.org/2013/05/creating-differential-backups-with-hard-links-and-rsync/

# "LATEST_BACKUP" - имя файла, в котором хранится имя папки с последним
# успешно созданным бэкапом. Если этот файл удалить, бэкап будет
# создаваться чистый, без жестких ссылок.

# Осбождение диска можно производить удалением папок со старыми бэкапами
# вручную, при этом освободится столько места, сколько занимали inode и
# файлы, отсутствующие в новых бэкапах. Имеет смысл удалять старые бэкапы
# или такие бэкапы, файлы из которых отсутствуют в других. Если удаляется
# последняя жесткая ссылка на файл, это место освобождается.

# rsync по умолчанию определяет изменения по размеру и дате модификации.
# Если они не изменились, файл может быть не добавлен в новый бэкап,
# хотя такой вариант не слишком вероятен. Можно защититься, сравнивая
# хеши (опция -c), но это может быть дольше.

# коды возврата скрипта:
# 1 -- Бэкап $NEW уже существует
# 2 -- rsync вернул не 0 или 24
# 0 -- иное (обычно успешно созданный бэкап)

# Рекомендуется хранить копию этого файла с прописанными настройками в BACKUP_DIR


######################################################
### НАСТРОЙКИ ###
# тут можно изменять как надо
# кроме этого стоит обратить внимание на ключи rsync ниже,
# например, чтобы исключить ненужное из бэкапов или не писать логи

# определяем папку, куда складываем бэкапы
BACKUP_DIR=/run/media/tsyg/f31eb047-4a69-497f-a09f-26a2029b248d/tsyg-pc_bkps

# папка, которую нужно копировать
# Если ставить / в конце, то содержимое будет прямо в папке бэкапов, иначе
# внутри каждого бэкапа будет папка с названием как SOURCE
# /bkps/2018-01-01/... vs /bkps/2018-01-01/tsyg/...
SOURCE=/home/tsyg

# устанавливаем название папки для нового бэкапа.
# экранирование теперь вроде учтено.
## нормальные варианты:
#NEW=`date --iso-8601`          # 2018-03-27
#NEW=`date --iso-8601=seconds`  # 2018-03-27T21:05:54+03:00
NEW=`date +%Y-%m-%d_%H-%M`     # 2018-03-27_17-30
#NEW=`date +%Y%m%d%H%M%S`       # 20180327173032
#NEW=`uuidgen`                  # d5baa846-f916-43da-9fb0-820e2e680c49


######################################################
### СОЗДАНИЕ БЭКАПА ###

echo "Бэкапим $SOURCE в $BACKUP_DIR, бэкап: $NEW"

# Проверяем, были ли бэкапы, в процессе создания которых произошли сбои rsync.
# их можно переименовать, это будет быстрее, чем копировать жесткими ссылками.
if [ -f "$BACKUP_DIR/BAD_BACKUP" ]; then
    BAD=$(cat $BACKUP_DIR/BAD_BACKUP)
    mv "$BACKUP_DIR/$BAD" "$BACKUP_DIR/$NEW"
    mv "$BACKUP_DIR/rsync-logs/$BAD.log" "$BACKUP_DIR/rsync-logs/$NEW.log"
    rm "$BACKUP_DIR/BAD_BACKUP"

# если начатых бэкапов нет, проверяем, на основе какого можно сделать
elif [ -f "$BACKUP_DIR/LATEST_BACKUP" ]; then
    LATEST=$(cat $BACKUP_DIR/LATEST_BACKUP)

    # надо проверить, что такого бэкапа еще нет, иначе они засрутся и
    # потом придется разгребать
    if [ $NEW != $LATEST ]; then
        # копируем поледний бэкап с сохранением всех атрибутов и
        # использованием жестких ссылок
        cp -al "$BACKUP_DIR/$LATEST" "$BACKUP_DIR/$NEW"
    else
        echo "Ошибка! Бэкап $NEW уже существует."
        exit 1
    fi
fi

# готовим папку для логов, на случай, если ее еще нет
mkdir -p "$BACKUP_DIR/rsync-logs/"

# создаем бэкап. Если rsync видит, что старый и новый файлы отличаются
# -- создает копию не изменяя содержимое старого бэкапа, иначе -- не
# трогает. Файлы, удаленные в бэкапируемой бапке, удаляются из бэкапа.
rsync -a --delete --info=PROGRESS2 \
    --log-file="$BACKUP_DIR/rsync-logs/$NEW.log" \
    "$SOURCE" "$BACKUP_DIR/$NEW"
# Ключи добавлять перед строчкой "$SOURCE" "$BACKUP_DIR/$NEW"
#    --exclude "BAD/trash_dir"\ # можно исключить ненужные папки
#    --exclude "*.zip"\         # и/или все файлы по шаблону
#    --delete-excluded\         # и удалять их из создаваемых бэкапов,
                                # а иначе они останутся как были до исключения

rsync_exit_code=$?
# если бэкап создан успешно (или в момент его создания какие-то файлы
# пропали -- код 24), прописываем его в файл LATEST_BACKUP
if [ $rsync_exit_code -eq 0 ] || [ $rsync_exit_code -eq 24 ]; then
    echo $NEW > "$BACKUP_DIR/LATEST_BACKUP"
else
    echo $NEW > "$BACKUP_DIR/BAD_BACKUP"
    echo "Ошибка: бэкап создан некорректно и будет перезаписан при следующем запуске"
    echo "Если вы хотите его оставить, удалите файл BAD_BACKUP"
    exit 2
fi

# Создание заготовки для следующего бэкапа, можно отключить флагом --fast
if [[ $* != *--fast* ]]
then
    echo "Бэкап создан. Создаем основу для следующего."
    cp -al "$BACKUP_DIR/$NEW" "$BACKUP_DIR/COPY"
    echo "COPY" > "$BACKUP_DIR/BAD_BACKUP"
fi
